package net.yyge.apksigner;

import net.yyge.manifest.editor.AndroidManifest;
import net.yyge.manifest.xml2axml.AxmlUtils;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

public class BuildApk {

    private static final String TAG = "BuildApk";

    public static void main(String[] args) throws Exception {
        boolean success = build();
        if (success) {
            System.out.println("✅ APK 构建与签名成功！");
        } else {
            System.err.println("❌ APK 构建失败！");
        }
    }

    public static boolean build() throws IOException {
        // 1. 获取 APK 路径
        String apkPath = getApkPath();
        File apkFile = new File(apkPath);
        if (!apkFile.exists()) {
            System.err.println(TAG + " | 源 APK 文件不存在: " + apkPath);
            return false;
        }

        // 2. 构建临时目录
        File tempDir = buildTempDir("001");
        if (tempDir == null || !tempDir.exists()) {
            System.err.println(TAG + " | 无法创建临时目录！");
            return false;
        }
        System.out.println(TAG + " | 临时目录: " + tempDir.getAbsolutePath());

        // 3. 解压 APK
        if (!unzipApk(apkPath, tempDir)) {
            System.err.println(TAG + " | 解压 APK 失败！");
            return false;
        }

        // 4. 重新打包
        File dataBuildPath = getDataBuildPath();
        File file = new File(dataBuildPath, "zip/zip.apk");
        File zipFile;
        try {
             zipFile = ZipUtils.zipDir(tempDir, file);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

        //对齐 后的文件
//         File alignFile = new File(dataBuildPath, "zip/align.apk");
//        // FileOutputStream zipOut = new FileOutputStream(alignFile);
//
//            System.out.println("最终对齐状态: " + alignFile);
        //Test.zipAlign(zipFile.getAbsolutePath(),alignFile.getAbsolutePath());



        // 5. 签名
        File keystoreFile = readAssetsFile("test.jks");
        if (!keystoreFile.exists()) {
            System.err.println(TAG + " | 签名密钥文件不存在: " + keystoreFile.getAbsolutePath());
            return false;
        }
        System.out.println("✅ 密钥文件存在: " + keystoreFile.getAbsolutePath());

        File signApkFile = getSignApkFile();
        ApkSigner apkSigner = new ApkSigner(zipFile, signApkFile);
        apkSigner.useDefaultSignatureVersion = false;
        apkSigner.v1SigningEnabled = true;
        apkSigner.v2SigningEnabled = true;
        apkSigner.v3SigningEnabled = true;
        apkSigner.v4SigningEnabled = false;
        apkSigner.zipAlign = true;

        apkSigner.signRelease(keystoreFile, "123456789", "test", "123456789");
        System.out.println("✅ 签名完成: " + signApkFile.getAbsolutePath());

        // 6. 可选：清理临时目录（调试时可注释）
        // deleteDirectory(tempDir);

        return true;
    }

    private static String getApkPath() {
        return "build/app-debug.apk";
    }

    // ✅ 修复：buildTempDir 会强制删除旧目录并重建
    private static File buildTempDir(String tempId) {
        File buildDir = getDataBuildPath();
        File tempParentDir = new File(buildDir, "temp");

        // ✅ 彻底删除整个 temp 目录
        deleteDirectory(tempParentDir);

        File targetDir = new File(tempParentDir, tempId);
        if (!targetDir.mkdirs()) {
            System.err.println(TAG + " | 无法创建目标目录: " + targetDir.getAbsolutePath());
            return null;
        }
        return targetDir;
    }

    // ✅ 新增：递归删除目录（解决“拒绝访问”）
    private static boolean deleteDirectory(File directory) {
        if (directory == null || !directory.exists()) return true;

        File[] files = directory.listFiles();
        if (files != null) {
            for (File file : files) {
                if (file.isDirectory()) {
                    deleteDirectory(file);
                } else {
                    if (!file.delete()) {
                        System.err.println(TAG + " | 无法删除文件: " + file.getAbsolutePath());
                    }
                }
            }
        }
        return directory.delete();
    }

    private static File getSignApkFile() {
        File tempDir = new File(getDataBuildPath(), "debug");
        File apkFile = new File(tempDir, "sign-debug.apk");

        if (!tempDir.exists() && !tempDir.mkdirs()) {
            System.err.println(TAG + " | 无法创建输出目录: " + tempDir.getAbsolutePath());
            return null;
        }
        return apkFile;
    }

    // ✅ 修复：unzipApk 增加健壮性
    private static boolean unzipApk(String apkPath, File destDir) {
        File apkFile = new File(apkPath);
        try (FileInputStream fis = new FileInputStream(apkFile);
             ZipInputStream zis = new ZipInputStream(fis)) {

            byte[] buffer = new byte[8192];
            ZipEntry entry;
            int fileCount = 0;

            while ((entry = zis.getNextEntry()) != null) {
                String entryName = entry.getName();

                // ✅ 跳过空条目或根目录
                if (entryName == null || entryName.isEmpty() || entryName.equals("/") || entryName.equals("\\")) {
                    zis.closeEntry();
                    continue;
                }

                // ✅ 跳过目录条目（以 / 结尾）
                if (entryName.endsWith("/")) {
                    File dir = new File(destDir, entryName);
                    if (!dir.exists() && !dir.mkdirs()) {
                        System.err.println(TAG + " | 无法创建目录: " + dir.getAbsolutePath());
                    }
                    zis.closeEntry();
                    continue;
                }

                File outputFile = new File(destDir, entryName);
                File parentDir = outputFile.getParentFile();
                if (parentDir != null && !parentDir.exists()) {
                    if (!parentDir.mkdirs()) {
                        System.err.println(TAG + " | 无法创建父目录: " + parentDir.getAbsolutePath());
                    }
                }

                // ✅ 读取数据
                byte[] data = getZipFileByte(zis, buffer);
                zis.closeEntry();

                // ✅ 处理特殊文件
                if ("AndroidManifest.xml".equals(outputFile.getName())) {
                    data = processManifest(data);
                }

                // ✅ 写入文件（会检查是否为目录）
                byteWriteFile(outputFile, data);
                fileCount++;
            }

            System.out.println(TAG + " | 解压完成，共 " + fileCount + " 个文件");
            return true;

        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }

    // ✅ 处理 AndroidManifest.xml
    private static byte[] processManifest(byte[] axmlData) {
        try {
            AndroidManifest manifest = AndroidManifest.loadAXML(axmlData);
            manifest.getActivity().setMainActivity("com.androlua.Welcome");
            File file = new File("ORG.xml");
            String xml = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8);
//            String xml = manifest.toXML();
//            String xml =
            System.out.println(xml);
            return AxmlUtils.encode(xml);
        } catch (Exception e) {
            System.err.println(TAG + " | 修改 AndroidManifest.xml 失败，使用原始数据");
            e.printStackTrace();
            return axmlData;
        }
    }

    // ✅ 修复：getZipFileByte 异常处理
    public static byte[] getZipFileByte(ZipInputStream zis, byte[] buffer) throws IOException {
        try (ByteArrayOutputStream byteWrite = new ByteArrayOutputStream()) {
            int len;
            while ((len = zis.read(buffer)) > 0) {
                byteWrite.write(buffer, 0, len);
            }
            return byteWrite.toByteArray();
        }
    }

    // ✅ 修复：byteWriteFile 确保目录存在 + 异常抛出
    public static void byteWriteFile(File file, byte[] data) throws IOException {
        // ✅ 检查：目标不能是目录
        if (file.exists() && file.isDirectory()) {
            throw new IOException("无法写入：目标是一个目录，不能写入文件数据: " + file.getAbsolutePath());
        }

        // ✅ 检查：不能是 null 或空路径
        if (file.getParent() == null) {
            throw new IOException("无效路径，无法写入: " + file.getAbsolutePath());
        }

        Path parent = file.toPath().getParent();
        if (parent != null && !Files.exists(parent)) {
            Files.createDirectories(parent);
        }

        try (FileOutputStream fos = new FileOutputStream(file)) {
            fos.write(data);
        }
    }

    public static File getDataBuildPath() {
        return new File("build");
    }

    // ✅ 修复：folderToApk
public static File folderToApk(File sourceDir) throws IOException {
    File outputFile = getSignApkFile();
    if (outputFile.exists() && !outputFile.delete()) {
        throw new IOException("无法删除已存在的输出文件: " + outputFile.getAbsolutePath());
    }

    if (!sourceDir.exists() || !sourceDir.isDirectory()) {
        throw new FileNotFoundException("源目录不存在或不是目录: " + sourceDir.getAbsolutePath());
    }

    try (FileOutputStream fos = new FileOutputStream(outputFile);
         ZipOutputStream zos = new ZipOutputStream(fos)) {

        // 使用标准路径分隔符
//        String rootPath = sourceDir.getCanonicalPath(); // 获取标准路径
//        //File rootFile = new File(rootPath);

        zipDirectory(sourceDir, outputFile.getAbsolutePath(), zos);
    }

    // ✅ 验证输出文件
    if (outputFile.length() == 0) {
        throw new IOException("打包失败：生成的 APK 文件大小为 0，请检查源目录是否为空或路径计算错误");
    }

    System.out.println(TAG + " | 重新打包完成: " + outputFile.getAbsolutePath() + " (大小: " + outputFile.length() + " 字节)");
    return outputFile;
}

private static void zipDirectory(File currentDir, String rootPath, ZipOutputStream zos) throws IOException {
    File[] files = currentDir.listFiles();
    if (files == null) return;

    for (File file : files) {
        if (file.isDirectory()) {
            zipDirectory(file, rootPath, zos); // 递归
        } else {
            // ✅ 正确计算相对于 rootPath 的路径
            String canonicalRoot = rootPath.replace('\\', '/');
            String canonicalFile = file.getCanonicalPath().replace('\\', '/');

            if (!canonicalFile.startsWith(canonicalRoot)) {
                throw new IOException("文件不在源目录内: " + canonicalFile);
            }

            // 计算相对路径
            String zipEntryName = canonicalFile.substring(canonicalRoot.length() + 1); // +1 跳过分隔符

            // ✅ 确保路径正确
            ZipEntry zipEntry = new ZipEntry(zipEntryName);
            zipEntry.setSize(file.length());
            zipEntry.setTime(System.currentTimeMillis()); // 或 file.lastModified()

            zos.putNextEntry(zipEntry);

            try (FileInputStream fis = new FileInputStream(file)) {
                byte[] buffer = new byte[8192];
                int len;
                while ((len = fis.read(buffer)) > 0) {
                    zos.write(buffer, 0, len);
                }
            }

            zos.closeEntry();

            System.out.println("已添加: " + zipEntryName + " -> " + file.length() + " bytes");
        }
    }
}
    // ✅ 修复：readAssetsFile
    public static File readAssetsFile(String filePath) {
        File file = new File(filePath); // 直接读项目根目录下的 test.jks
        return file;
    }

    // ✅ 可选：Stream 工具类（可独立成类）
    public static class StreamToFile {
        public static void write(InputStream in, File dest) throws IOException {
            Path parent = dest.toPath().getParent();
            if (parent != null) Files.createDirectories(parent);

            try (InputStream input = in;
                 OutputStream output = new FileOutputStream(dest)) {
                byte[] buffer = new byte[8192];
                int len;
                while ((len = input.read(buffer)) > 0) {
                    output.write(buffer, 0, len);
                }
            }
        }
    }
}